// Copyright (c) 2008 Matthew Johnson
// Licensed under the MIT License (see license.txt)
package actionml.interaction {
	import actionml.Global;

	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.events.FocusEvent;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.ui.Keyboard;

	public class DragAndDropInteractions {
		public static function initialize() {
			Global.initializeExtension(DragAndDropInteractions, DisplayObject);
		}
		public static var instanceMethods:Object = {
			makeInstanceMaker: function(options:Object = null) {
				if (this.isAlready("InstanceMaker")) return;
				options ||= {};
				this.makeButton(options);
				this.instanceClass = Class["forName"](options["instanceSymbol"]);
				this.instanceTargetArea = this.getValueForKeypath(options["instanceTargetArea"] || "instanceTargetArea");
				if (this.instanceTargetArea) {
					this.instanceTargetBounds = this.instanceTargetArea.getBounds(this.instanceTargetArea);
					if (this.instanceTargetArea.overIndicator) {
						this.instanceTargetArea.overIndicator.visible = false;
					}
				} else {
					// TODO: trace error
				}
				var o:DisplayObject = this;
				this.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent) {
					o.instantiateAndDrag(e) ;
				});
				this.addEventListener(KeyboardEvent.KEY_DOWN, function(e:KeyboardEvent) {
					if (e.keyCode == Keyboard.UP || e.keyCode == Keyboard.DOWN || e.keyCode == Keyboard.LEFT || e.keyCode == Keyboard.RIGHT) { 
						o.instantiateAndDrag(e);
					}
				});
				this.dropTargets = [];

			},
			instantiateAndDrag: function(e:Event) {
				var instance:DisplayObject = new this.instanceClass();
				this.parent.addChild(instance);
				if (e is MouseEvent) {
					instance.x = this.parent.mouseX;
					instance.y = this.parent.mouseY;
				} else {
					instance.x = (this.x + this.width) / 2;
					instance.y = (this.y + this.height) / 2;
					this.stage.focus = instance;
				}
				instance.maker = this;
				if (this.originalInstanceXML) {
					instance.configureFromXML(this.originalInstanceXML);
				}
				instance.makeButton();
				instance.enableMouseDragging("beginDragInstance", "updateDragInstance", "endDragInstance");
				instance.enableKeyboardDragging("beginDragInstance", "updateDragInstance", "abortDragInstance", "endDragInstance");
				instance.dispatchEvent(e);
			},
			beginDragInstance: function() {
				this.root["getCurtain"]().playTween({startAlpha: 0, alpha: 1, alwaysPlayFromStart: true});
				this.beginDrag();
				this.blockFocusIndicator = true;
				if (this.focusIndicator) {
					this.focusIndicator.visible = false;
				}
			},
			updateDragInstance: function() {
				if (this.parent == this.maker.instanceTargetArea) {
					var r:Rectangle = this.maker.instanceTargetBounds;
					var thisR:Rectangle = this.getRect(this.parent);
					if (thisR.left < r.left) {
						this.setLeft(r.left);
					} else if (thisR.right > r.right) {
						this.setRight(r.right);
					}
					if (thisR.top < r.top) {
						this.setTop(r.top);
					} else if (thisR.bottom > r.bottom) {
						this.setBottom(r.bottom);
					}
				}
				var newOverTargetArea:Boolean = this.hitTestObject(this.maker.instanceTargetArea);
				if (this.overTargetArea && !newOverTargetArea) {
					this.doEvent("dragOut");
					this.maker.instanceTargetArea.doEvent("dragOut");
				} else if (newOverTargetArea) {
					this.doEvent("dragOver");
					this.maker.instanceTargetArea.doEvent("dragOver");
				}
				this.overTargetArea = newOverTargetArea;
				if (this.maker.instanceTargetArea.overIndicator) {
					this.maker.instanceTargetArea.overIndicator.visible = newOverTargetArea;
				}
			},
			abortDragInstance: function() {
				this.updateDragInstance();				
				var o:DisplayObject = this;
				if (this.parent == this.maker.instanceTargetArea) {
					this.playTween({x: this.originalX, y: this.originalY, duration: 0.5, callWhenFinished: function() {o.updateFocusIndicator();}});
				} else {
					this.playTween({alpha: 0, duration: 0.5, callWhenFinished: function() {o.remove();}});
				}
				this.root["getCurtain"]().playTween({alpha: 0});
				if (this.maker.instanceTargetArea.overIndicator) {
					this.maker.instanceTargetArea.overIndicator.visible = false;
				}
				this.blockFocusIndicator = false;
				this.dragging = false;
			},
			endDragInstance: function() {
				this.updateDragInstance();
				if (this.overTargetArea && this.parent != this.maker.instanceTargetArea) {
					var p:Point = this.getPositionInCoordinateSpaceOfObject(this.maker.instanceTargetArea);
					this.x = p.x;
					this.y = p.y;
					this.maker.instanceTargetArea.addChild(this);
					this.moveToFront();
				} else if (!this.overTargetArea) {
					this.abortDragInstance();
				}
				this.root["getCurtain"]().playTween({alpha: 0});
				if (this.maker.instanceTargetArea.overIndicator) {
					this.maker.instanceTargetArea.overIndicator.visible = false;
				}
				this.blockFocusIndicator = false;
				this.dragging = false;
			},
			makeSortItem: function(options:Object = null) {
				if (this.isAlready("SortItem")) return;
				options ||= {};
				this.makeButton(options);
				this.parent.sortItems ||= [];
				this.parent.sortItems.push(this);
				this.enableMouseDragging("beginSortItem", "updateSortItem", "endSortItem");				this.enableKeyboardDragging("beginSortItem", "updateSortItem", "endSortItem", "endSortItem");				this.dropX = this.x;
				this.dropY = this.y;
			},
			beginSortItem: function() {				if (!this.dragging) {					if (!this.retainLayering) {							this.moveToFront();					}					this.originalX = this.x;					this.originalY = this.y;					this.dragging = true;					this.movedByKeyboard = false;					this.doEvent("dragStart");				}			},			updateSortItem: function() {
				var coordinate:String = "y";				
				var beforeFunction:String = "getTop";
				var afterFunction:String = "getBottom";				
				if (this.parent.sortDirection == "horizontal") {
					coordinate = "x";
					beforeFunction = "getLeft";
					afterFunction = "getRight";
				}
				for each (var s:DisplayObject in this.parent.sortItems) {
					var foundNewPosition:Boolean = false;
					var newDropX:Number = 0;
					var newDropY:Number = 0;
					if (this[beforeFunction]() < s.getCenter()[coordinate] && this[beforeFunction]() > s[beforeFunction]() && !s.moving) {
						foundNewPosition = true;
						newDropX = s.dropX;
						newDropY = s.dropY;
						s.moving = true;
					}
					if (this[afterFunction]() > s.getCenter()[coordinate] && this[afterFunction]() < s[afterFunction]() && !s.moving) {
						foundNewPosition = true;
						newDropX = s.dropX;
						newDropY = s.dropY
						s.moving = true;
					}
					if (foundNewPosition) {
						s.dropX = this.dropX;
						s.dropY = this.dropY;
						s.playTween({x: this.dropX, y: this.dropY, duration:.5, callWhenFinished: function() {
							s.moving = false;						
						}});
						this.dropX = newDropX;
						this.dropY = newDropY;
						break;
					}
				}
			},
			endSortItem: function() {
				this.playTween({x: this.dropX, y: this.dropY, duration:.5});
				this.dragging = false;
			},
			makeDragItem: function(options:Object = null) {
				if (this.isAlready("DragItem")) return;
				options ||= {};
				options["dropTargets"] ||= "";
				this.makeButton(options);
				if (this.dropIndicator) {
					this.dropIndicator.visible = false;
				}
				if (this.invalidDropIndicator) {
					this.invalidDropIndicator.visible = false;
				}
				this.dropTargets = [];
				for each (var dropTarget in options["dropTargets"].split(/\s*,\s*/)) {
					this.dropTargets.push(this.getValueForKeypath(dropTarget));
				}
				this.enableMouseDragging("beginDrag", "updateDragItem", "endDragItem");
				this.enableKeyboardDragging("beginDrag", "updateDragItem", "abortDragItem", "endDragItem");
			},
			makeDropTarget: function(options:Object = null) {
				if (this.isAlready("DropTarget")) return;
				options ||= {};
				this.root["dropTargets"] ||= [];
				this.root["dropTargets"].push(this);
				if (this.overIndicator) {
					this.overIndicator.visible = false;
				}
				if (this.dropIndicator) {
					this.dropIndicator.visible = false;
				}
				if (this.invalidDropIndicator) {
					this.invalidDropIndicator.visible = false;
				}
				if (this.dropMarker) {
					this.dropMarker.visible = false;
				}
				for (var i:int = 0; i < (this.numChildren - 1); i++) {
					var child:DisplayObject = this.getChildAt(i);
					if ("name" in child && child.name && child.name.endsWith("DropMarker")) {
						child.visible = false;
					}
				}
			},
			beginDrag: function() {
				if (!this.dragging) {
					if (!this.retainLayering) {	
						this.moveToFront();
					}
					if (!("originalX" in this)) {						this.originalX = this.x;
					}					if (!("originalY" in this)) {						this.originalY = this.y;
					}					this.dragging = true;
					this.movedByKeyboard = false;
					this.doEvent("dragStart");
				}
			},
			updateDragItem: function() {
				var dropTarget:DisplayObject;
				var overlapArea:Number = 0;
				for each (var target:DisplayObject in this.root["dropTargets"]) {
					if (this.hitTestObject(target)) {
						var area:Number = this.getBounds(this.stage).intersection(target.getBounds(this.stage)).getArea();
						if (area > overlapArea) {
						dropTarget = target;
							overlapArea = area;
						}
					}
				}
				if (dropTarget && dropTarget != this.currentDropTarget) {
					if (this.currentDropTarget) {
						if (this.currentDropTarget.overIndicator) {
							this.currentDropTarget.overIndicator.visible = false;
						}
						this.currentDropTarget.doEvent("dragOut");
						this.doEvent("dragOut");
					}
					if (dropTarget.overIndicator) {
						dropTarget.overIndicator.visible = true;
					}
					this.currentDropTarget = dropTarget;
					this.currentDropTarget.doEvent("dragOver");
					this.doEvent("dragOver");
				} else if (!dropTarget && this.currentDropTarget) {
					if (this.currentDropTarget.overIndicator) {
						this.currentDropTarget.overIndicator.visible = false;
						this.currentDropTarget.doEvent("dragOut");												this.doEvent("dragOut");					}
					this.currentDropTarget = null;
				}
			},
			endDragItem: function() {
				this.updateDragItem();
				this.movedByKeyboard = false;
				if (this.currentDropTarget && this.currentDropTarget.overIndicator) {
					this.currentDropTarget.overIndicator.visible = false;
				}
				if (this.dropTargets.contains(this.currentDropTarget)) {
					this.dropHit();
				} else {
					this.dropMiss();						
				}	
				this.dragging = false;
			},
			abortDragItem: function() {
				this.doEvent("abortDrag");
				var o:DisplayObject = this;
				this.playTween({x: this.originalX, y: this.originalY, duration: 0.5, callWhenFinished: function() {o.updateFocusIndicator();}});
			},
			dropMiss: function() {
				this.doEvent("dropMiss");
				if (this.currentDropTarget) {
					this.currentDropTarget.doEvent("dropMiss");
				}
				this.doEvent("dragEnd");
				var o:DisplayObject = this;
				this.playTween({x: this.originalX, y: this.originalY, duration: 0.5, callWhenFinished: function() {o.updateFocusIndicator();}});
				if (this.currentDropTarget && this.currentDropTarget.invalidDropIndicator) {
					this.currentDropTarget.invalidDropIndicator.visible = true;
					this.currentDropTarget.invalidDropIndicator.alpha = 0;
					this.currentDropTarget.invalidDropIndicator.playTween({startAlpha:0, alpha:1})
						.playWhenFinished({alpha:0});
				}
				if (this.invalidDropIndicator) {
					this.invalidDropIndicator.visible = true;
					this.invalidDropIndicator.alpha = 0;
					this.invalidDropIndicator.playTween({startAlpha:0, alpha:1})
						.playWhenFinished({alpha:0});
				}
			},
			dropHit: function() {
				this.currentDropTarget.doEvent("dropHit");
				var oParent:DisplayObject = this.parent;
				var o:DisplayObject = this;
				if (this.currentDropTarget && this.currentDropTarget.dropIndicator) {
					this.currentDropTarget.dropIndicator.visible = true;
					this.currentDropTarget.dropIndicator.alpha = 0;
					this.currentDropTarget.dropIndicator.playTween({startAlpha:0, alpha:1})
						.playWhenFinished({alpha:0});
				}
				if (this.dropIndicator) {
					this.dropIndicator.visible = true;
					this.dropIndicator.alpha = 0;
					this.dropIndicator.playTween({startAlpha:0, alpha:1})
						.playWhenFinished({alpha:0});
				}
				var x:Number = this.currentDropTarget[this.name + "X"];
				if (!x && this.currentDropTarget[this.name + "DropMarker"]) {
					//x = this.currentDropTarget.getPositionInCoordinateSpaceOfObject(this.parent).x +
					x = this.currentDropTarget.x +
						this.currentDropTarget[this.name + "DropMarker"].x;
				}
				if (!x) {
					x = this.currentDropTarget.dropX;
				}
				if (!x && this.currentDropTarget.dropMarker) {
					//x = this.currentDropTarget.getPositionInCoordinateSpaceOfObject(this.parent).x +
					this.currentDropTarget.x +
						this.currentDropTarget.dropMarker.x;
				}
				if (!x) {
					x = this.currentDropTarget.x + (this.currentDropTarget.width - this.width) / 2;
				}
				var y:Number = this.currentDropTarget[this.name + "Y"];
				if (!y && this.currentDropTarget[this.name + "DropMarker"]) {
					//y = this.currentDropTarget.getPositionInCoordinateSpaceOfObject(this.parent).y +
					y = this.currentDropTarget.y +
						this.currentDropTarget[this.name + "DropMarker"].y;
				}
				if (!y) {
					y = this.currentDropTarget.dropY;
				}
				if (!y && this.currentDropTarget.dropMarker) {
					//y = this.currentDropTarget.getPositionInCoordinateSpaceOfObject(this.parent).y +
					y = this.currentDropTarget.y +
						this.currentDropTarget.dropMarker.y;
				}
				if (!y) {
					y = this.currentDropTarget.y + (this.currentDropTarget.height - this.height) / 2;
				}
				var p:Point = this.getPositionInCoordinateSpaceOfObject(this.currentDropTarget.parent);
				this.currentDropTarget.parent.addChild(this);
				this.setPosition(p);
				this.playTween({
					x: x, 
					y: y,
					duration: 0.5, 
					callWhenFinished: function() {
						o.doEvent("dropHit");
					}
				});	
				this.hit.removeEventListener(MouseEvent.MOUSE_DOWN, this.mouseDownDragHandler);
				if (!this.allowFocusAfterHit) {
					this.hit.buttonMode = false;
					this.hit.useHandCursor = this.parent.useHandCursor;
					this.tabEnabled = false;
					this.forceMouseOut();
					this.blockMouseFocus();
				}
				if (!this.currentDropTarget.allowMultipleDragItems) {
					this.root["dropTargets"]["remove"](this.currentDropTarget);
					if (this.root["dropTargets"].length == 0) {
						oParent.doEvent("allDropTargetsHit");
						this.currentDropTarget.parent.doEvent("allDropTargetsHit");
					}
				}
			},
			enableMouseDragging: function(beginDrag:String, updateDrag:String, endDrag:String) {
				var o:DisplayObject = this;
				this.mouseDownDragHandler = function(e:MouseEvent) {
					o[beginDrag]();
					o.startDrag();
					var mouseDownHandler:Function = arguments.callee;
					Global.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
					function mouseMoveHandler(e:MouseEvent) {
						o[updateDrag]();
					}
					Global.stage.addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent) {
						o.stage.focus = null;
						o[endDrag]();
						o.stopDrag();
						Global.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
						Global.stage.removeEventListener(MouseEvent.MOUSE_UP, arguments.callee);
					});
				};
				this.hit.addEventListener(MouseEvent.MOUSE_DOWN, this.mouseDownDragHandler);
			},
			enableKeyboardDragging: function(beginDrag:String, updateDrag:String, abortDrag:String, endDrag:String) {
				var o:DisplayObject = this;
				this.addEventListener(FocusEvent.FOCUS_OUT, function(e:FocusEvent) {
					if (o.movedByKeyboard) {
						o[abortDrag]();
					}
				});
				this.addEventListener(KeyboardEvent.KEY_DOWN, function(e:KeyboardEvent) {
					if (e.keyCode == Keyboard.UP) {
						o[beginDrag]();
						o.movedByKeyboard = true;
						o.y--;
						if (e.ctrlKey) {
							o.y -= 9;
						}
						o[updateDrag]();
					}
					if (e.keyCode == Keyboard.DOWN) {
						o[beginDrag]();
						o.movedByKeyboard = true;
						o.y++;
						if (e.ctrlKey) {
							o.y += 9;
						}
						o[updateDrag]();
					}
					if (e.keyCode == Keyboard.RIGHT) {
						o[beginDrag]();
						o.movedByKeyboard = true;
						o.x++;
						if (e.ctrlKey) {
							o.x += 9;
						}
						o[updateDrag]();
					}
					if (e.keyCode == Keyboard.LEFT) {
						o[beginDrag]();
						o.movedByKeyboard = true;
						o.x--;
						if (e.ctrlKey) {
							o.x -= 9;
						}
						o[updateDrag]();
					}
					if (e.keyCode == Keyboard.ENTER) {
						o[endDrag]();
					}
					if (e.keyCode == Keyboard.ESCAPE) {
						o[abortDrag]();
					}
				});	
			}
		}
	}
}